/**
 * Copyright (c) 2006-2012 Las Venturas Playground, LVP Mineground
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program.
 * If not, see <http://www.gnu.org/licenses/>.
 */

package mineground.core;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;

/**
 * The Module registry owns all the modules available in the code. After they are being registered,
 * this registry will create an instance of them and scan the class for any methods annotated with
 * the @MessageHandler annotation, which will be registered with the Message Router.
 * 
 * Instances of modules are private to this class, enforcing proper message passing methodologies
 * for cross-module communication. Modules do have the ability to unregister themselves by invoking
 * the "unregister" method on the ModuleControllerBase class.
 */
public class ModuleRegistry {
    /**
     * Maintain a list of registered modules since all of these are owned by this class. This list
     * will be iterated over when the gamemode is being shut down.
     */
    private ArrayList<ModuleControllerBase> mModuleList = new ArrayList<ModuleControllerBase>();
    
    /**
     * The Message Router instance is what all message handlers will be registered to. The Message
     * Loop uses the same router instance to distribute messages to, but we own it.
     */
    private MessageRouter mMessageRouter;
    
    /**
     * Initiate a new Module Registry object. In most cases there should only be one instance of
     * the registry, which is owned and created by the Mineground base class.
     */
    public ModuleRegistry() {
        ModuleControllerBase.setModuleRegistry(this);
        mMessageRouter = new MessageRouter();
    }
    
    /**
     * Register a new class with the registry. A new instance will be created and inserted to the
     * Module list. After this, all methods declared in the class will be iterated over in search
     * for any that listen to messages, which will be registered with the message router.
     * Registering new modules may be done on all threads.
     * 
     * @param moduleClass Class object of the module which should be instantiated.
     */
    public boolean register(Class<? extends ModuleControllerBase> moduleClass) {
        ModuleControllerBase instance = null;
        try {
            instance = moduleClass.newInstance();
        } catch (Exception e) {
            return false;
        }
        
        // Insert this module in the Module List. Since another thread may be busy removing a
        // module from the list, this needs to happen in a synchronized block.
        synchronized (mModuleList) {
            mModuleList.add(instance);
        }
        
        // Iterate over all methods in the class and attempt to find any annotated as a message
        // handler. When found, register the handler with the Message Router.
        Method[] methodArray = moduleClass.getDeclaredMethods();
        for (int index = 0, length = methodArray.length; index < length; ++index) {
            if (methodArray[index].isAnnotationPresent(MessageHandler.class) == false)
                continue;
            
            MessageHandler handlerType = methodArray[index].getAnnotation(MessageHandler.class);
            mMessageRouter.registerHandler(handlerType.message(), instance, methodArray[index],
                handlerType.priority());
        }
        
        return true;
    }
    
    /**
     * Unregister a single module. There are two code-paths which can start unregistering of a
     * module, namely code within the module's controller itself, and when the whole game is being
     * shut down and all modules have to be unregistered.
     * 
     * @param moduleInstance Instance of the module which should be removed.
     */
    public void unregister(ModuleControllerBase moduleInstance, boolean removeFromList) {
        mMessageRouter.unregisterHandlersForController(moduleInstance);
        moduleInstance.onDispose();
        
        // Remove the instance from the Module List if we have to, marking it for clean-up by
        // Java's garbage collector. The shutdown() method will want to do this manually.
        if (removeFromList == false)
            return;
        
        synchronized (mModuleList) {
            mModuleList.remove(moduleInstance);
        }
    }
    
    /**
     * When the entire game is being shut down, we want to dispose of all registered modules to make
     * sure that they can properly dispose of the logic they own.
     */
    public void shutdown() {
        for (Iterator<ModuleControllerBase> iter = mModuleList.iterator(); iter.hasNext(); ) {
            unregister(iter.next(), false);
            iter.remove();
        }
        
        // Reset the known registry as we can no longer rely on our functionality.
        ModuleControllerBase.setModuleRegistry(null);
    }
    
    /**
     * Retrieve the instance of the Message Router owned by this class. In general only the Message
     * Loop should be needing this, as it'll be used to actually distribute messages.
     */
    public MessageRouter getMessageRouter() {
        return mMessageRouter;
    }
}